// INSE6260-ATM Simulator Project

/**
 * Model package contains core code of ATMS
 */
package com.inse6260.atms.model;

import java.util.Hashtable;
import java.util.Date;

/**
 * An instance of this class provides ATMS log in and transaction features.
 * @author dimitri.tiago
 * @version 1.0.0
 */
public class Atms
{
    // constant declarations
    private int ATMS_INITIAL_BALANCE = 1000000;

    // field declarations
    private int             atmsBalance;
    private BankClient      currentBankClient;
    private BankDatabase    bankDatabase ;
    private String          receipt;

    // default no-argument constructor
    public Atms()
    {
        // initialize atms fields
        atmsBalance         = ATMS_INITIAL_BALANCE;
        bankDatabase        = new BankDatabase();
        currentBankClient   = new BankClient();
        receipt             = "";
    }

     /**
     * Validates that bank client is authorized to operate ATMS.
     * @param PIN bank client 4-6 digit PIN.
     * @return boolean value indicating authorization status (authorized or unauthorized)
     */
    public boolean authorize(int pin)
    {
        // set bank client pin
        currentBankClient.setPin(pin);
        currentBankClient.setAuthorized(false);

        // determine if bank client is authorized to use ATMS
        bankDatabase.authorizeBankClient(currentBankClient);
  
        // if bank client blocked...
        if (currentBankClient.getBlocked() == true)
        {
            // blocked. not authorized and reset pin
            currentBankClient.setPin(0);
            return false;
        }

        // authorization failed. determine if card number is blocked
        if (currentBankClient.getNoOfLoginRetries() > 3)
        {
            // block bank client card number
            currentBankClient.setBlocked(true);
            bankDatabase.blockBankClient(currentBankClient);
        }

        // if authorized...
        if (currentBankClient.getAuthorized() == true)
        {
            // authorized
            return true;
        }
        else
        {
            // reset pin and ensure bank client is un-authorized
            currentBankClient.setPin(0);
        }
        
        // return un-authorized
        return false;
    }

    public String[] getAccInformation()
    {
        BankAccount[] accList;
        String[]      accInformation;

        // get accounts list
        accList =  bankDatabase.getAccounts(currentBankClient.getCardNumber());

        if (accList.length > 0)
        {
            accInformation = new String[accList.length];

            for (int i = 0; i < accList.length; i++)
            {
                accInformation[i] = accList[i].getAccType() + "-" +
                        accList[i].getAccNumber() + "-"  + accList[i].getAccBalance();
            }
        }
        else
        {
            return null;
        }
        
        return accInformation;
    }

    public String getAccStatement(int accountNumber)
    {
        TransactionLineItem[] transLineItemArray = bankDatabase.getTransLineItems(accountNumber);
        StringBuffer statement = new StringBuffer();

        String stmtHeader = String.format("%-30s%-30s%-30s%-30s%s\n", "Transaction Id", "Account Number", "Transaction Type", "Amount", "Date");
        statement.append(stmtHeader);

        for (TransactionLineItem li : transLineItemArray)
        {
            statement.append(li.toString());
        }

        return statement.toString();
    }

    /**
     * Executes ATMS transaction
     * @param transType transaction type (e.g. withdrawal, deposit, transfer, pay-bill, etc..)
     * @param transParams <code>Hashtable<String, String></code> containing transaction parameters in K,V pairs
     * @return transaction result (e.g. success, insufficient funds, etc..)
     */
    public String executeTransaction(String transType, Hashtable<String, String> transParams)
    {
        // local variables
        int     accountNumber     = Integer.parseInt(transParams.get("account"));
        float   currAccBalance    = bankDatabase.getAccountBalance(accountNumber);
        String  accType           = transParams.get("accType");
        String  transResult       = "";

        // determine type of transaction
        if (transType.equalsIgnoreCase("withdrawal"))
        {
            // withdrawal transaction

            float   withdrawAmount    = Float.parseFloat(transParams.get("amount"));
            float   maxOneTimeWithAmt = currentBankClient.getMaxOneTimeWithAmt();
            float   dailyWithLimit    = currentBankClient.getDailyTransLimit();

            // validate that there is enough cash in ATMS.
            if (withdrawAmount > atmsBalance)
            {
                // generate receipt
                transResult = "Insufficient funds in ATM.";
                printReceipt(accountNumber, accType, null, transResult);
                return transResult;
            }

            // validate that there is enough cash in account
            if (withdrawAmount > currAccBalance)
            {
                // generate receipt
                transResult = "Insufficient account balance.";
                printReceipt(accountNumber, accType, null, transResult);
                return transResult;
            }

            // validate bank client maximum one time withdrawal amount not exceeded
            if (withdrawAmount > maxOneTimeWithAmt)
            {
                // generate receipt
                transResult = "Exceeded maximum one time withdrawal amount.";
                printReceipt(accountNumber, accType, null, transResult);
                return transResult;
            }

            // validate bank client does not exceed maximum daily withdrawal limit
            if (withdrawAmount > dailyWithLimit)
            {
                // generate receipt
                transResult = "Exceeded maximum daily withdrawal amount.";
                printReceipt(accountNumber, accType, null, transResult);
                return transResult;
            }

            // sufficient funds in ATMS, proceed with withdrawal.
            Transaction withdrawal = new Withdrawal(bankDatabase, accountNumber, withdrawAmount );
            withdrawal.execute();
            atmsBalance -= withdrawAmount;                                              // subtract dispensed cash from atm
            bankDatabase.debitDailyTransLimit(currentBankClient, withdrawAmount);       // debit daily withdrawal limit
            transResult = "success";

            // generate receipt
            printReceipt(accountNumber, accType , "-" + String.valueOf(withdrawAmount), transResult);
        }

        // deposit
        if (transType.equalsIgnoreCase("deposit"))
        {
            // deposit transaction

            float   depositAmount    = Float.parseFloat(transParams.get("amount"));

            Transaction deposit = new Deposit(bankDatabase, accountNumber, depositAmount );
            deposit.execute();
            transResult = "success";

            // generate receipt
            printReceipt(accountNumber, accType , "+" + String.valueOf(depositAmount), transResult);
        }

        // transfer
        if (transType.equalsIgnoreCase("transfer"))
        {
            // deposit transaction

            float   transferAmt = Float.parseFloat(transParams.get("amount"));
            int     tgtAcc      = Integer.parseInt(transParams.get("tgtAccount"));

            // validate account balance
            if (transferAmt > currAccBalance)
            {
                // generate receipt
                transResult = "Insufficient account balance.";
                printReceipt(accountNumber, accType, null, transResult);
                return transResult;
            }

            Transaction transfer = new Transfer(bankDatabase, accountNumber, tgtAcc, transferAmt);
            transfer.execute();
            transResult = "success";

            // generate receipt
            String target = "Transferred to account# " + tgtAcc + ": +$" + transferAmt;
            printReceipt(accountNumber, accType,"-" + String.valueOf(transferAmt), transResult, target);
        }

        return transResult;
    }

    /**
     * Returns validation of card number result
     * @param currentCardNumber bank client card number
     * @return string with validation result
     */
    public String validateCardNumber(int currentCardNumber)
    {
        // local varibales
        Boolean exists   = false;

        currentBankClient.setCardNumber(currentCardNumber);
        exists = bankDatabase.cardNoExists(currentBankClient);

        if (exists == true)
        {
            return String.format("%s", "Success");
        }
        else
        {
            currentBankClient.setCardNumber(0);     // reset bank client card number
            return String.format("%s", "Card number not found in system.");
        }
    }

    /**
     * Returns reason for authorization failure.
     * @return authorization failure
     */
    public String getAuthFailureReason()
    {
        // local variable declarations
        String failureReason = "";

        // determine failure reason
        if (currentBankClient.getBlocked() == true)
        {
            // account blocked
            failureReason = String.format("%s", "Bank client card is blocked.");
            return failureReason;
        }
        else if (!bankDatabase.cardNoExists(currentBankClient))
        {
            // account not found
            failureReason = String.format("%s\n%s", "Bank card number not found in system.");
            return failureReason;
        }
        else
        {
            // incorrect PIN
            failureReason = String.format("%s", "Incorrect PIN.");
            return failureReason;
        }        
    }

    private void printReceipt(int account, String accType, String amount, String transResult)
    {
        printReceipt(account, accType, amount, transResult, null);
    }

    private void printReceipt(int account, String accType, String amount, String transResult, String tgtAcct)
    {
        Date myDate      = new Date();
        String currDate  = myDate.toString();
        Float accBalance = bankDatabase.getAccountBalance(account);

        if (transResult.equalsIgnoreCase("success"))
        {
            if (tgtAcct == null)
            {
                receipt = String.format("%s%s%40s\n%s:%19s\n%s%21.2f ", "Account#: ", account, currDate, accType, amount, "Balance:", accBalance);
            }
            else
            {
                receipt = String.format("%s%s%40s\n%s:%19s\n%s%21.2f \n%s", "Account#: ", account, currDate, accType, amount, "Balance:", accBalance, tgtAcct);
            }
        }
        else
        {
            receipt = String.format("%s%s%40s\n%s:%44s\n%s%21.2f ", "Account#: ", account, currDate, accType, transResult, "Balance:", accBalance);
        }
    }

    public String getReciept()
    {
        return receipt;
    }

    /**
     * Set ATMS balance.
     * @param newAtmsBalance new Atms balance
     */
    public void setAtmsBalance(int newAtmsBalance)
    {
        this.atmsBalance = newAtmsBalance;
    }

    /**
     * Returns ATMS balance.
     * @return Atms balance
     */
    public int getAtmsBalance()
    {
        return atmsBalance;
    }

    public void resetAtm()
    {
        currentBankClient = new BankClient();
        receipt = "";
    }
  }
